﻿using System;
using System.Linq;

namespace Emerald
{
    public class TranspositionTable
    {
        #region Members

        private static TranspositionTable internalTranspositionTable;
        private readonly GameState[] table = new GameState[104729];
        private static readonly ulong[, ,] HashConversionTable;

        #endregion

        #region Properties

        public static TranspositionTable Instance
        {
            get { return internalTranspositionTable ?? (internalTranspositionTable = new TranspositionTable()); }
        }

        #endregion

        #region Constructors

        private TranspositionTable()
        {

        }

        static TranspositionTable()
        {
            Random random = new Random();

            HashConversionTable = new ulong[21, 20, 5];

            for (int x = 0; x < 21; x++)
            {
                for (int y = 0; y < 20; y++)
                {
                    for (int z = 0; z < 5; z++)
                    {
                        ulong value = (Convert.ToUInt64(random.Next()) << 32 | Convert.ToUInt64(random.Next()));
                        HashConversionTable[x, y, z] = value;
                    }
                }
            }
        }

        #endregion

        #region Methods

        public ulong CalculateHashCode(GameState gs)
        {
            ulong hashValue = 0;

            foreach (Tile tile in gs.Board.BoardArray.Tiles)
            {
                // empty spot, white unmarked, white marked, red unmarked, red marked

                hashValue ^= HashConversionTable[tile.Location.X - gs.Board.BoardArray.TopLeftBoundary.X,
                                                 tile.Location.Y - gs.Board.BoardArray.TopLeftBoundary.Y,
                                                 tile.TileType];
            }

            return hashValue;
        }

        public ulong CalculateHashCode(GameState gs, Tile t)
        {
            return HashConversionTable[t.Location.X - gs.Board.BoardArray.TopLeftBoundary.X,
                                       t.Location.Y - gs.Board.BoardArray.TopLeftBoundary.Y,
                                       t.TileType];
        }

        public ulong CalculateHashCode(GameState gs, byte x, byte y)
        {
            return HashConversionTable[x - gs.Board.BoardArray.TopLeftBoundary.X,
                                       y - gs.Board.BoardArray.TopLeftBoundary.Y,
                                       gs.Board.BoardArray[x, y].TileType];
        }

        public ulong CalculateHashCode(GameState gs, byte x, byte y, byte tileType)
        {
            return HashConversionTable[x - gs.Board.BoardArray.TopLeftBoundary.X,
                                       y - gs.Board.BoardArray.TopLeftBoundary.Y,
                                       tileType];
        }

        public ulong CalculateHashCode(GameState gs, BoardPoint bp)
        {
            return CalculateHashCode(gs, bp.X, bp.Y);
        }

        public ulong CalculateHashCode(GameState gs, BoardPoint bp, byte tileType)
        {
            return CalculateHashCode(gs, bp.X, bp.Y, tileType);
        }

        public bool AddGamestate(GameState gs)
        {
            int tablePosition = index(gs);
            if (table[tablePosition] == null)
            {
                table[tablePosition] = gs;
                return true;
            }
            else if (gs.Depth > table[tablePosition].Depth)
            {
                table[tablePosition] = gs;
                return true;
            }
            return false;
        }

        public GameState GetGamestate(int hashval)
        {
            return table[hashval % table.Length];
        }

        #endregion

        internal bool Contains(GameState g, int depth)
        {
            return null != table[index(g)] && table[index(g)].Depth >= depth;
        }

        private int index(GameState g)
        {
            return (int)(g.HashCode % (ulong)table.Length);
        }

        public int GetValue(GameState gameState)
        {
            return table[index(gameState)].EvalValue;
        }

        public int Count
        {
            get
            {
                return table.Count(t => t != null);
            }
        }

        public float FillRatio
        {
            get
            {
                return Count / (float)table.Length;
            }
        }
    }
}
